using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

/*
	Classes to make threading and interaction with a UI easier.
	
	The goal is to have a thread proc that can feed state and prgress back to the UI
	and respond to an abort request.
	There are two threading patterns supported:
	1. A long process that happens in steps. The UI reports the steps somehow.
	2. A long process without steps. All that the main program
	   requires is a note of the success or failure of the process.
*/
namespace WinFormThreading
{
	// class to pass state between interface and thread proc
	public class ThreadProcessState
	{

		// process states
		// ready / working / finished / aborted
		public enum ThreadState { ready, working, done, cancelled, errored }
		ThreadState m_state = ThreadState.ready;

		// progress
		int m_totalSteps = 1;     // default one step
		int m_currentStep = 0;    // 
		object m_userData = null;  // user data
		Exception m_Exception;

		public ThreadProcessState() { }

		public int TotalSteps { get { return this.m_totalSteps; } set { this.m_totalSteps = value; } }
		public int CurrentStep { get { return this.m_currentStep; } set { this.m_currentStep = value; } }
		public ThreadState State { get { return this.m_state; } }
		public bool IsDone { get { return (this.m_state == ThreadState.done); } }
		public bool IsCancelled { get { return (this.m_state == ThreadState.cancelled); } }
		public bool IsErrored { get { return (this.m_state == ThreadState.errored); } }
		public bool IsAborted { get { return (this.m_state == ThreadState.cancelled || this.m_state == ThreadState.errored); } }
		public bool IsWorking { get { return (this.m_state == ThreadState.working); } }
		public object UserData { get { return this.m_userData; } set { this.m_userData = value; } }
		public Exception Exception { get { return this.m_Exception; } set { this.m_Exception = value; } }

		// called as the thread is kicked off
		public void onStartWork()
		{
			this.m_currentStep = 0;
			this.m_state = ThreadState.ready;
		}
		// interface for setting state
		public void cancel() { this.m_state = ThreadState.cancelled; }  // called mainly the UI
		public void start() { this.m_state = ThreadState.working; }  // called by the thread class
		public void finish() { this.m_state = ThreadState.done; }    // called by the thread class
		public void setError(Exception ex)
		{
			this.m_Exception = ex;
			if (this.m_state != ThreadState.cancelled) // don't change state if already cancelled
				this.m_state = ThreadState.errored;
		}
	}

	// declare the callback function signature
	// users of the class below pass in a function with this pattern
	delegate void ProgressStateDelegate(ThreadProcessState processState);

	/*
		wraps the nitty-gritty of a background task with feedback
	*/
	public class WorkerBase
	{
		// the members below are passed in the constructor by the user

		// Usually a form or a winform control that implements "Invoke/BeginInvode"
		ContainerControl m_sender = null;
		// The delegate method (callback) on the sender to call
		Delegate m_senderDelegate = null;
		// the object to hold state information
		ThreadProcessState m_processState;

		// Constructor used by caller using ThreadPool (untested)
		public WorkerBase() { }

		// Constructor called by calle using ThreadPool OR ThreadStart
		// see above for params
		public WorkerBase(ContainerControl sender, Delegate senderDelegate, ThreadProcessState state)
		{
			this.m_sender = sender;
			this.m_senderDelegate = senderDelegate;
			this.m_processState = state;
		}

		// called to start the work
		public void KickOffThread()
		{
			Thread t = new Thread(new ThreadStart(this.RunProcess));
			t.IsBackground = true;    //make them a daemon - prevent thread callback issues
			t.Start();
		}

		// Method for ThreadPool QueueWorkerItem (untested)
		public void RunProcess(object obj)
		{
			Thread.CurrentThread.IsBackground = true; //make them a daemon
			object[] objArray = (object[])obj;
			this.m_sender = (System.Windows.Forms.Form)objArray[0];
			this.m_senderDelegate = (System.Delegate)objArray[1];
			this.m_processState = (ThreadProcessState)objArray[2];

			this.LocalRunProcess();
		}

		// Method for ThreadStart delegate
		public void RunProcess()
		{
			Thread.CurrentThread.IsBackground = true; //make them a daemon
			this.m_processState.onStartWork();
			this.LocalRunProcess();
		}

		/// <summary>
		/// Local Method for the actual work.
		/// </summary>
		protected virtual void LocalRunProcess()
		{
			this.m_processState.start();
			// do nothing implementation that just loops
			for (int step = 0; step < this.m_processState.TotalSteps &&
							   !this.m_processState.IsAborted; ++step)
			{
				this.m_processState.CurrentStep = step;
				this.DoWorkStep(this.m_processState);
				// state is either working or aborted.
				// note: the completing step will not callback 
				// to avoid sending two callbacks with the done state.
				if (step < this.m_processState.TotalSteps - 1 &&
					this.m_processState.State == ThreadProcessState.ThreadState.working)
					this.Callback();
			}
			// send the done call back unless already aborted
			if (!this.m_processState.IsAborted)
			{
				this.m_processState.finish();
			}
			this.Callback();
		}
		// Overridable by the inheriting class to specialise the work done
		protected virtual void DoWorkStep(ThreadProcessState processState)
		{
			// default is a small delay - only good for testing progress lines.
			Thread.Sleep(200);
		}

		// invoked during and on completion 
		protected void Callback()
		{
			// may have to snapshot thread state to avoid > 1 done message
			this.m_sender.BeginInvoke(this.m_senderDelegate, new object[] { this.m_processState });
		}
	}
}
